import _ from 'underscore';
import $ from 'jquery';

$.ajaxSettings.traditional = true;

export class Descriptor {
  constructor(desc) {
    this._url = _.isFunction(desc.url)?desc.url:function(){return desc.url};
    this._paramsValidator = _.chain(desc.params)
                             .omit(v => !_.isFunction(v.validate))
                             .mapObject(v => v.validate)
                             .value();
    this._defaultParams = _.mapObject(desc.params,v => v.defaultValue);
    this._headers = desc.headers||{};
    this._optionParams = desc.optionParams;
    this._method = desc.method||'get';
    this._dataType=desc.dataType||'json';
    this._contentType = (desc.contentType||'application/x-www-form-urlencoded') + '; charset=UTF-8'
  }

  makeParams(paramsFns) {
    return _.chain(paramsFns)
            .reduce((acc,v) => {
              let p = _.isFunction(v)?v.call(acc):v;
              return _.extend({},acc,_.omit(p,value => _.isUndefined(value)));
            },this._defaultParams)
            .mapObject((v,k,a) => _.isFunction(v)?v.call(a):v,this)
            .value();
  }

  validateParams(params) {
    return _.chain(this._paramsValidator)
            .map((validFn,key) => {
              let message = validFn(params[key],params);
              return message?{key,message}:null;
            })
            .compact()
            .value();
  }

  makeRequest(params) {
    let data = _.omit.apply(_,[params].concat(this._optionParams));
    return {
      url:this._url(params),
      type:this._method,
      data:this._contentType.indexOf('application/json')>-1?JSON.stringify(data):data,
      dataType:this._dataType,
      cache:false,
      contentType:this._contentType,
      headers:this._headers
    }
  }
}

export default (descriptor) => {
  let desc = new Descriptor(descriptor);

  return () => {
    let paramsFns = [].slice.call(arguments,0);

    return {
      post:preFilter => {
        let params = desc.makeParams(paramsFns);
        let ajaxOption = desc.makeRequest(params);
        let validateResults = desc.validateParams(params);

        if(validateResults.length){
          return $.Deferred()
                  .reject(_.extend({},validateResults[0],{
                    errors:_.chain(validateResults)
                            .map(i => [i.key,i.message])
                            .object()
                            .value()
                  }),params);
        }

        if(preFilter){
          ajaxOption = preFilter(ajaxOption);
        }

        this.lastUrl = ajaxOption.url;
        this.lastParams = params;

        let deferred = $.Deferred();
        deferred.notify(ajaxOption,params);

        this._xhr = $.ajax(ajaxOption)
                     .done(data => deferred.resolve(data,params))
                     .fail(err => deferred.reject(err,params))
                     .always(
                         () => {
                           this._xhr = null;
                         }.bind(this)
                      );

        return deferred.promise();
      },
      fetch:preFilter => {
        let params = desc.makeParams(paramsFns);
        let ajaxOption = desc.makeRequest(params);

        if(this._xhr){
          this._xhr.abort();
          this._xhr = null;
        }

        if(preFilter){
          ajaxOption = preFilter(ajaxOption);
        }

        this.lastUrl = ajaxOption.url;
        this.lastParams = params;

        let deferred = $.Deferred();

        deferred.notify(ajaxOption,params);
        this._xhr = $.ajax(ajaxOption)
                     .done(data => deferred.resolve(data,params))
                     .fail(err => deferred.reject(err,params))
                     .always(() => {
                         this._xhr = null;
                       }.bind(this)
                      );

        return deferred.promise();
      }
    }
  }
}